﻿using System;
using System.Net;
using System.Net.Sockets;
using Nutilities.Net;
using SharedLibrary;

namespace Auktionshuset___Server
{
    public class Client : ThreadedSocket
    {
        private User _user;
        private readonly Broadcaster _bc;

        private readonly UserMonitor _userMonitor;
        private readonly AuctionMonitor _auctionMonitor;

        public Client(Socket socket, Broadcaster broadcaster, UserMonitor userMonitor, AuctionMonitor auctionMonitor) : base(socket)
        {
            _bc = broadcaster;
            _userMonitor = userMonitor;
            _auctionMonitor = auctionMonitor;

            Name = "Client[" + ((IPEndPoint) socket.RemoteEndPoint).Address + "]";
        }

        // This is where the magic happends
        protected override void MessageReceived(byte message)
        {
            switch (message)
            {
                case Command.REGISTER:
                    HandleRegister();
                    break;
                case Command.LOGIN:
                    HandleLogin();
                    break;
                case Command.LOGOUT:
                    HandleLogout();
                    break;
                case Command.UPDATE_AUCTION:
                    HandleRequestAuctionUpdate();
                    break;
                case Command.GET_AUCTIONS:
                    HandleGetAuctions();
                    break;
                case Command.POST_AUCTION:
                    HandlePostAuction();
                    break;
                case Command.PLACE_BID:
                    HandlePlaceBid();
                    break;
                case Command.AUCTION_SUBSCRIBE:
                    HandleAuctionSubscribe();
                    break;
                case Command.AUCTION_UNSUBSCRIBE:
                    HandleAuctionUnsubscribe();
                    break;
            }
        }

        // Responses
        private void SendErrorResponse(string message)
        {
            Console.WriteLine(message);

            Writer.Write(Command.ERROR);
            Writer.Write(message);
            Writer.Flush();
        }

        private void SendErrorResponse(byte message, string errorMessage)
        {
            Writer.Write(message);
            SendErrorResponse(errorMessage);
        }

        private void SendOkResponse()
        {
            Console.WriteLine("OK");

            Writer.Write(Command.OK);
            Writer.Flush();
        }

        private void SendOkResponse(byte message)
        {
            Writer.Write(message);
            SendOkResponse();
        }

        // Message Handlers - Responds to input
        private void HandleRegister()
        {
            string username = Reader.ReadString();
            string password = Reader.ReadString();
            string alias = Reader.ReadString();

            Console.WriteLine("Registration attempt: " + username + ", " + alias);

            if (_userMonitor.Exists(username))
            {
                SendErrorResponse("A user with that username already exists.");
                return;
            }

            _userMonitor.Add(new User(username, password, alias));
            SendOkResponse();

            Terminate();
        }

        private void HandleLogin()
        {
            string username = Reader.ReadString();
            string password = Reader.ReadString();

            Console.WriteLine("Login attempt: " + username);

            if (_userMonitor.Exists(username))
            {
                User user = _userMonitor.Get(username);
                if (user.Password == password)
                {
                    if (user.Active)
                    {
                        SendErrorResponse("This user is already logged in.");
                        return;
                    }

                    user.Active = true;
                    _user = user;
                    Writer.Write(Command.OK);
                    user.WriteTo(Writer);
                    Writer.Flush();
                    return;
                }
            }

            SendErrorResponse("Invalid username or password");
        }

        private void HandleLogout()
        {
            Console.WriteLine(_user.Username + " logged out");

            _user.Active = false;
            Terminate(); // Kills the thread
        }

        private void HandleRequestAuctionUpdate()
        {
            int auctionId = Reader.ReadInt32();

            Auction auction = _auctionMonitor.Get(auctionId);

            Writer.Write(Command.UPDATE_AUCTION);
            auction.UpdateTo(Writer);
            Writer.Flush();
        }

        private void HandleGetAuctions()
        {
            Console.WriteLine("Request all auctions");

            BinaryWriterUtility.Write(Writer, _auctionMonitor.GetAuctions());
            Writer.Flush();
        }

        private void HandleGetAuction()
        {
            int id = Reader.ReadInt32();
            
            Console.WriteLine("Request single auction: " + id);

            _auctionMonitor.Get(id).WriteTo(Writer);
            Writer.Flush();
        }

        private void HandlePostAuction()
        {
            var auction = new Auction
            {
                Title = Reader.ReadString(),
                Description = Reader.ReadString(),
                Owner = _user,

                ImageBytes = Reader.ReadBytes(Reader.ReadInt32()),

                NextBid = Reader.ReadDecimal(),

                Starts = DateTime.FromBinary(Reader.ReadInt64()),

                Active = true
            };

            _auctionMonitor.Add(auction);

            Console.WriteLine("Posted auction");

            _bc.Broadcast(Command.POST_AUCTION, auction);
        }

        private void HandlePlaceBid()
        {
            Console.WriteLine("Bid placed");
            int auctionId = Reader.ReadInt32();
            decimal bid = Reader.ReadDecimal();

            Auction auction = _auctionMonitor.Get(auctionId);
            if (auction.NextBid == bid)
            {
                auction.CurrentBid = bid;
                auction.HighestBidder = _user;
                auction.NextBid = bid + 50;
                auction.StatusMessage = "";

                if (auction.Thread != null)
                {
                    auction.Thread.Terminate();
                }

                auction.Thread = new AuctionThread(auction, _bc);
                auction.Thread.Start();

                Console.WriteLine("OK");

                _bc.UpdateTo(Command.UPDATE_AUCTION, auction, _auctionMonitor.GetSubscribers(auction));
            }
            else
            {
                SendErrorResponse(Command.PLACE_BID, "Your bid is too low");
            }
        }

        private void HandleAuctionSubscribe()
        {
            int auctionId = Reader.ReadInt32();
            _auctionMonitor.SubscribeTo(auctionId, this);
        }

        private void HandleAuctionUnsubscribe()
        {
            int auctionId = Reader.ReadInt32();
            _auctionMonitor.UnsubscribeTo(auctionId, this);
        }
    }
}
